/***************************************************************************/
/*                                                                         */
/*  afglobal.c                                                             */
/*                                                                         */
/*    Auto-fitter routines to compute global hinting values (body).        */
/*                                                                         */
/*  Copyright 2003-2011 by                                                 */
/*  David Turner, Robert Wilhelm, and Werner Lemberg.                      */
/*                                                                         */
/*  This file is part of the FreeType project, and may only be used,       */
/*  modified, and distributed under the terms of the FreeType project      */
/*  license, LICENSE.TXT.  By continuing to use, modify, or distribute     */
/*  this file you indicate that you have read the license and              */
/*  understand and accept it fully.                                        */
/*                                                                         */
/***************************************************************************/


#include "afglobal.h"
#include "afdummy.h"
#include "aflatin.h"
#include "afcjk.h"
#include "afindic.h"
#include "afpic.h"

#include "aferrors.h"

#ifdef FT_OPTION_AUTOFIT2
#include "aflatin2.h"
#endif

#ifndef FT_CONFIG_OPTION_PIC

/* when updating this table, don't forget to update          */
/* AF_SCRIPT_CLASSES_COUNT and autofit_module_class_pic_init */

/* populate this list when you add new scripts */
static AF_ScriptClass const af_script_classes[] =
        {
                &af_dummy_script_class,
#ifdef FT_OPTION_AUTOFIT2
                &af_latin2_script_class,
#endif
                &af_latin_script_class,
                &af_cjk_script_class,
                &af_indic_script_class,
                NULL  /* do not remove */
        };

#endif /* !FT_CONFIG_OPTION_PIC */

/* index of default script in `af_script_classes' */
#define AF_SCRIPT_LIST_DEFAULT  2
/* a bit mask indicating an uncovered glyph       */
#define AF_SCRIPT_LIST_NONE     0x7F
/* if this flag is set, we have an ASCII digit    */
#define AF_DIGIT                0x80


/*
 *  Note that glyph_scripts[] is used to map each glyph into
 *  an index into the `af_script_classes' array.
 *
 */
typedef struct AF_FaceGlobalsRec_
{
    FT_Face face;
    FT_Long glyph_count;    /* same as face->num_glyphs */
    FT_Byte *glyph_scripts;

    AF_ScriptMetrics metrics[AF_SCRIPT_MAX];

} AF_FaceGlobalsRec;


/* Compute the script index of each glyph within a given face. */

static FT_Error
af_face_globals_compute_script_coverage( AF_FaceGlobals globals )
{
    FT_Error error = AF_Err_Ok;
    FT_Face face = globals->face;
    FT_CharMap old_charmap = face->charmap;
    FT_Byte *gscripts = globals->glyph_scripts;
    FT_UInt ss, i;


    /* the value AF_SCRIPT_LIST_NONE means `uncovered glyph' */
    FT_MEM_SET( globals->glyph_scripts,
                AF_SCRIPT_LIST_NONE,
                globals->glyph_count );

    error = FT_Select_Charmap( face, FT_ENCODING_UNICODE );
    if ( error )
    {
        /*
         *  Ignore this error; we simply use the default script.
         *  XXX: Shouldn't we rather disable hinting?
         */
        error = AF_Err_Ok;
        goto Exit;
    }

    /* scan each script in a Unicode charmap */
    for ( ss = 0; AF_SCRIPT_CLASSES_GET[ ss ]; ss++ )
    {
        AF_ScriptClass clazz = AF_SCRIPT_CLASSES_GET[ ss ];
        AF_Script_UniRange range;


        if ( clazz->script_uni_ranges == NULL)
        {
            continue;
        }

        /*
         *  Scan all unicode points in the range and set the corresponding
         *  glyph script index.
         */
        for ( range = clazz->script_uni_ranges; range->first != 0; range++ )
        {
            FT_ULong charcode = range->first;
            FT_UInt gindex;


            gindex = FT_Get_Char_Index( face, charcode );

            if ( gindex != 0 &&
                 gindex < ( FT_ULong ) globals->glyph_count &&
                 gscripts[ gindex ] == AF_SCRIPT_LIST_NONE )
            {
                gscripts[ gindex ] = ( FT_Byte ) ss;
            }

            for ( ;; )
            {
                charcode = FT_Get_Next_Char( face, charcode, &gindex );

                if ( gindex == 0 || charcode > range->last )
                {
                    break;
                }

                if ( gindex < ( FT_ULong ) globals->glyph_count &&
                     gscripts[ gindex ] == AF_SCRIPT_LIST_NONE )
                {
                    gscripts[ gindex ] = ( FT_Byte ) ss;
                }
            }
        }
    }

    /* mark ASCII digits */
    for ( i = 0x30; i <= 0x39; i++ )
    {
        FT_UInt gindex = FT_Get_Char_Index( face, i );


        if ( gindex != 0 && gindex < ( FT_ULong ) globals->glyph_count )
        {
            gscripts[ gindex ] |= AF_DIGIT;
        }
    }

    Exit:
    /*
     *  By default, all uncovered glyphs are set to the latin script.
     *  XXX: Shouldn't we disable hinting or do something similar?
     */
    {
        FT_Long nn;


        for ( nn = 0; nn < globals->glyph_count; nn++ )
        {
            if (( gscripts[ nn ] & ~AF_DIGIT ) == AF_SCRIPT_LIST_NONE )
            {
                gscripts[ nn ] &= ~AF_SCRIPT_LIST_NONE;
                gscripts[ nn ] |= AF_SCRIPT_LIST_DEFAULT;
            }
        }
    }

    FT_Set_Charmap( face, old_charmap );
    return error;
}


FT_LOCAL_DEF( FT_Error )
af_face_globals_new( FT_Face face,
                     AF_FaceGlobals *aglobals )
{
    FT_Error error;
    FT_Memory memory;
    AF_FaceGlobals globals = NULL;


    memory = face->memory;

    if ( !FT_ALLOC( globals, sizeof( *globals ) +
                             face->num_glyphs * sizeof( FT_Byte )))
    {
        globals->face = face;
        globals->glyph_count = face->num_glyphs;
        globals->glyph_scripts = ( FT_Byte * ) ( globals + 1 );

        error = af_face_globals_compute_script_coverage( globals );
        if ( error )
        {
            af_face_globals_free( globals );
            globals = NULL;
        }
    }

    *aglobals = globals;
    return error;
}


FT_LOCAL_DEF( void )
af_face_globals_free( AF_FaceGlobals globals )
{
    if ( globals )
    {
        FT_Memory memory = globals->face->memory;
        FT_UInt nn;


        for ( nn = 0; nn < AF_SCRIPT_MAX; nn++ )
        {
            if ( globals->metrics[ nn ] )
            {
                AF_ScriptClass clazz = AF_SCRIPT_CLASSES_GET[ nn ];


                FT_ASSERT( globals->metrics[ nn ]->clazz == clazz );

                if ( clazz->script_metrics_done )
                {
                    clazz->script_metrics_done( globals->metrics[ nn ] );
                }

                FT_FREE( globals->metrics[ nn ] );
            }
        }

        globals->glyph_count = 0;
        globals->glyph_scripts = NULL;  /* no need to free this one! */
        globals->face = NULL;

        FT_FREE( globals );
    }
}


FT_LOCAL_DEF( FT_Error )
af_face_globals_get_metrics( AF_FaceGlobals globals,
                             FT_UInt gindex,
                             FT_UInt options,
                             AF_ScriptMetrics *ametrics )
{
    AF_ScriptMetrics metrics = NULL;
    FT_UInt gidx;
    AF_ScriptClass clazz;
    FT_UInt script = options & 15;
    const FT_Offset script_max = sizeof( AF_SCRIPT_CLASSES_GET ) /
                                 sizeof( AF_SCRIPT_CLASSES_GET[ 0 ] );
    FT_Error error = AF_Err_Ok;


    if ( gindex >= ( FT_ULong ) globals->glyph_count )
    {
        error = AF_Err_Invalid_Argument;
        goto Exit;
    }

    gidx = script;
    if ( gidx == 0 || gidx + 1 >= script_max )
    {
        gidx = globals->glyph_scripts[ gindex ] & AF_SCRIPT_LIST_NONE;
    }

    clazz = AF_SCRIPT_CLASSES_GET[ gidx ];
    if ( script == 0 )
    {
        script = clazz->script;
    }

    metrics = globals->metrics[ clazz->script ];
    if ( metrics == NULL)
    {
        /* create the global metrics object when needed */
        FT_Memory memory = globals->face->memory;


        if ( FT_ALLOC( metrics, clazz->script_metrics_size ))
        {
            goto Exit;
        }

        metrics->clazz = clazz;

        if ( clazz->script_metrics_init )
        {
            error = clazz->script_metrics_init( metrics, globals->face );
            if ( error )
            {
                if ( clazz->script_metrics_done )
                {
                    clazz->script_metrics_done( metrics );
                }

                FT_FREE( metrics );
                goto Exit;
            }
        }

        globals->metrics[ clazz->script ] = metrics;
    }

    Exit:
    *ametrics = metrics;

    return error;
}


FT_LOCAL_DEF( FT_Bool )
af_face_globals_is_digit( AF_FaceGlobals globals,
                          FT_UInt gindex )
{
    if ( gindex < ( FT_ULong ) globals->glyph_count )
    {
        return ( FT_Bool ) ( globals->glyph_scripts[ gindex ] & AF_DIGIT );
    }

    return ( FT_Bool ) 0;
}


/* END */
